<!-- 3D地图组件 -->
<!-- 使用TresJS（Three.js的Vue封装库）实现3D地图可视化 -->
<template>
  <!-- TresGroup用于组织和管理3D对象 -->
  <TresGroup ref="tgRef">
    <!-- 遍历区域列表，根据类型渲染不同的3D元素 -->
    <template v-for="(item, index) in areaList" :key="`${index}`">
      <!-- 渲染地区名称文本标签 -->
      <Html
        v-if="item.type === 'Html'"
        v-bind="htmlState"
        :position="item.position"
      >
        <span :style="labelStyle">
          {{ item.name }}
        </span>
      </Html>

      <!-- 渲染地区图标（精灵图） -->
      <TresSprite
        v-if="item.type === 'Sprite'"
        :position="item.position"
        :scale="0.3"
        :renderOrder="1000"
      >
        <TresSpriteMaterial
          :color="0xff0000"
          :blending="THREE.NormalBlending"
          :map="pTexture"
        />
      </TresSprite>

      <!-- 渲染地区区域（3D挤压形状） -->
      <TresMesh
        v-if="item.type === 'Shape'"
        :name="item.name"
        :renderOrder="index"
        :pCenter="item.pCenter"
        @pointer-enter="pEnter"
        @pointer-leave="pLeave"
        @click="pClick"
      >
        <TresExtrudeGeometry
          :args="[item.shape, { depth: item.depth, bevelEnabled: false }]"
        />
        <TresMeshStandardMaterial
          :color="item.color"
          :emissive="0x000000"
          :roughness="0.45"
          :metalness="0.8"
          :transparent="true"
          :side="THREE.DoubleSide"
        />
      </TresMesh>

      <!-- 渲染地区边界线（双线效果：白色在上，黑色在下） -->
      <template v-if="item.type === 'Line'">
        <!-- 上层白色线条 -->
        <TresLine :renderOrder="index" :position-z="item.depth + 0.0001">
          <TresBufferGeometry :position="[item.points, 3]" />
          <TresLineBasicMaterial :color="0xffffff" :linewidth="0.5" />
        </TresLine>
        <!-- 下层黑色线条（产生描边效果） -->
        <TresLine :renderOrder="index" :position-z="-0.0001">
          <TresBufferGeometry :position="[item.points, 3]" />
          <TresLineBasicMaterial :color="0x000000" :linewidth="0.5" />
        </TresLine>
      </template>
    </template>
  </TresGroup>
</template>

<script setup>
//========================== 导入依赖 ==========================//
import { watchEffect, ref, computed } from "vue"; // Vue3 组合式API
import * as D3 from "d3-geo"; // 地理数据处理库
import * as THREE from "three"; // 3D渲染库
import { loadGeojson } from "@/components/three/common/request"; // 加载GeoJSON数据的工具函数
import { Html } from "@tresjs/cientos"; // TresJS扩展组件
import { useTexture, useTresContext, useRenderLoop } from "@tresjs/core"; // TresJS核心功能
import {
  computeBoundsTree,
  disposeBoundsTree,
  acceleratedRaycast,
} from "three-mesh-bvh"; // 用于优化射线检测的库
import { flyTo } from "@/components/three/common/utils"; // 相机飞行动画工具函数
import gsap from "gsap"; // 导入GSAP动画库

//========================== 组件属性定义 ==========================//
const props = defineProps({
  // 是否启用飞行功能的开关
  enableFlyTo: {
    type: Boolean,
    default: false, // 默认禁用
  },
  // GeoJSON文件路径
  geoJsonPath: {
    type: String,
    default: "src/components/three/json/32000_full.json",
  },
  // GeoJSON数据特征
  geoJsonFeature: {
    type: String,
    default: "features",
  },
  // 地区数值数据字段 (properties中的字段名)
  dataField: {
    type: String,
    default: "value",
  },
  // 颜色方案
  colorScheme: {
    type: String,
    default: "blueRed", // 可选: "blueRed", "rainbow", "heatmap"
  },
  // 地区数据 (可以外部传入地区数据对象，键为地区名，值为对应的数值)
  regionData: {
    type: Object,
    default: () => ({}),
  },
  // 是否使用对数比例尺(适用于数据差异很大的情况)
  useLogScale: {
    type: Boolean,
    default: false,
  },
  // 是否启用高度映射
  enableHeightMapping: {
    type: Boolean,
    default: false,
  },
  // 最大突出高度
  maxHeight: {
    type: Number,
    default: 0.5, // 默认最大高度
  },
  // 最小基础高度
  minHeight: {
    type: Number,
    default: 0.2, // 最小基础高度
  },
  // 文本标签字体大小
  labelFontSize: {
    type: Number,
    default: 12, // 默认字体大小
  },
  // 文本标签字体
  labelFontFamily: {
    type: String,
    default: "consolas", // 默认字体
  },
  // 文本标签粗细
  labelFontWeight: {
    type: String,
    default: "bold", // 默认粗体
  },
  // 区域高度变化动画时长（秒）
  heightAnimationDuration: {
    type: Number,
    default: 0.3, // 默认0.3秒
  },
  // 区域高度变化缓动函数
  heightAnimationEase: {
    type: String,
    default: "power2.out", // 默认缓出效果
  },
  // 区域悬停时高度增量
  heightHoverIncrement: {
    type: Number,
    default: 0.2, // 默认0.3单位高度增量
  },
});

//========================== 性能优化初始化 ==========================//
// 初始化three-mesh-bvh，用于加速射线检测（优化性能）
const initMeshBvh = () => {
  THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
  THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
  THREE.Mesh.prototype.raycast = acceleratedRaycast;
};
initMeshBvh();

//========================== 数据加载 ==========================//
// 加载GeoJSON地图数据，使用props中的值
const areaJson = await loadGeojson(props.geoJsonPath, props.geoJsonFeature);

// 加载图标纹理
const { map: pTexture } = await useTexture({
  map: "src/components/three/image/icon.png",
});

// 获取地图中心点
const center = areaJson[0].properties.centroid;

// 创建D3墨卡托投影，用于将地理坐标转换为平面坐标
const offsetXY = D3.geoMercator();
offsetXY.center(center).translate([0, 0]); // 设置投影中心和偏移

// 存储处理后的区域数据
const areaList = [];

//========================== 颜色方案定义 ==========================//
// 定义不同颜色方案的颜色数组
const colorSchemes = {
  blueRed: [
    "#2166ac",
    "#67a9cf",
    "#d1e5f0",
    "#f7f7f7",
    "#fddbc7",
    "#ef8a62",
    "#b2182b",
  ],
  rainbow: [
    "#2c7bb6",
    "#00a6ca",
    "#00ccbc",
    "#90eb9d",
    "#ffff8c",
    "#f9d057",
    "#f29e2e",
    "#e76818",
    "#d7191c",
  ],
  heatmap: [
    "#313695",
    "#4575b4",
    "#74add1",
    "#abd9e9",
    "#e0f3f8",
    "#ffffbf",
    "#fee090",
    "#fdae61",
    "#f46d43",
    "#d73027",
    "#a50026",
  ],
};

//========================== 数据处理函数 ==========================//
// 从地区名称获取数值数据
const getRegionValue = (name) => {
  // 优先使用外部传入的regionData
  if (props.regionData && props.regionData[name] !== undefined) {
    return props.regionData[name];
  }

  // 如果没有外部数据，生成随机值(0-100)，实际应用中可以替换为真实数据
  return Math.random() * 100;
};

// 计算所有地区的数值
const regionValues = computed(() => {
  const values = {};
  areaJson.forEach((feature) => {
    const name = feature.properties.name;
    values[name] = getRegionValue(name);
  });
  return values;
});

// 获取数值范围
const valueRange = computed(() => {
  const values = Object.values(regionValues.value);
  return {
    min: Math.min(...values),
    max: Math.max(...values),
  };
});

//========================== 颜色映射函数 ==========================//
// 创建颜色映射函数
const getColorForValue = (value) => {
  const { min, max } = valueRange.value;

  // 选择颜色方案
  const colors = colorSchemes[props.colorScheme] || colorSchemes.blueRed;

  // 计算归一化值 (0-1之间)
  let normalizedValue;
  if (props.useLogScale && min > 0 && max > 0) {
    // 使用对数比例尺
    const logMin = Math.log(min);
    const logMax = Math.log(max);
    const logValue = Math.log(Math.max(value, min)); // 确保不会出现负无穷
    normalizedValue = (logValue - logMin) / (logMax - logMin);
  } else {
    // 使用线性比例尺
    normalizedValue = (value - min) / (max - min || 1); // 避免除以零
  }

  // 将归一化值映射到颜色数组索引
  const index = Math.min(
    Math.floor(normalizedValue * colors.length),
    colors.length - 1
  );

  // 获取颜色
  return new THREE.Color(colors[index]);
};

//========================== 高度映射函数 ==========================//
// 计算地区高度函数
const getHeightForValue = (value) => {
  if (!props.enableHeightMapping) {
    // 如果未启用高度映射，返回随机高度（保持原有逻辑）
    // return Math.random() * 0.3 + 0.3;
    return 0.2;
  }

  const { min, max } = valueRange.value;

  // 计算归一化值 (0-1之间)
  let normalizedValue;
  if (props.useLogScale && min > 0 && max > 0) {
    // 使用对数比例尺
    const logMin = Math.log(min);
    const logMax = Math.log(max);
    const logValue = Math.log(Math.max(value, min)); // 确保不会出现负无穷
    normalizedValue = (logValue - logMin) / (logMax - logMin);
  } else {
    // 使用线性比例尺
    normalizedValue = (value - min) / (max - min || 1); // 避免除以零
  }

  // 计算高度：最小高度 + 归一化值 * 最大高度差
  return (
    props.minHeight + normalizedValue * (props.maxHeight - props.minHeight)
  );
};

//========================== 3D地图生成 ==========================//
// 处理地图数据，创建3D表示
const makeAreaPrimary = () => {
  areaJson.forEach((feature) => {
    // 获取地区名称
    const { centroid, oneCenter, center: Cc, name } = feature.properties;
    const { coordinates, type } = feature.geometry;

    // 获取地区中心点
    const point = centroid || oneCenter || Cc || [0, 0];

    // 获取地区数值
    const value = regionValues.value[name];

    // 根据数值生成颜色
    const color = getColorForValue(value).getHex();

    // 根据数值计算高度
    const depth = getHeightForValue(value);

    // 计算HTML标签位置
    const htmlPosition = offsetXY(point);
    htmlPosition[1] = -htmlPosition[1]; // Y轴反转
    htmlPosition[2] = depth; // 设置Z轴高度
    areaList.push({ type: "Html", position: htmlPosition, name });

    // 计算精灵图标位置
    const spritePosition = offsetXY(point);
    spritePosition[1] = -spritePosition[1] + 0.2; // Y轴反转并略微上移
    spritePosition[2] = depth + 0.22; // 比区域稍高的Z轴高度
    areaList.push({ type: "Sprite", position: spritePosition });

    // 处理区域坐标数据
    coordinates.forEach((coordinate) => {
      // 处理不同类型的多边形数据
      function fn(crdinate) {
        // 创建区域形状
        const shape = new THREE.Shape();
        crdinate.forEach((item, idx) => {
          const [x, y] = offsetXY(item);
          // 第一个点使用moveTo，后续点使用lineTo
          if (idx === 0) shape.moveTo(x, -y);
          else shape.lineTo(x, -y);
        });

        // 添加区域形状到列表，包含数值信息
        areaList.push({
          type: "Shape",
          shape,
          name,
          color,
          depth,
          pCenter: spritePosition,
          value: value, // 存储数值，可用于显示信息
        });

        // 创建边界线
        const points = [];
        crdinate.forEach((item) => {
          const [x, y] = offsetXY(item);
          points.push(x, -y, 0);
        });

        // 添加边界线到列表
        areaList.push({
          type: "Line",
          points: new Float32Array(points),
          depth,
        });
      }

      // 根据几何类型处理坐标
      if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
      if (type === "Polygon") fn(coordinate);
    });
  });
};
makeAreaPrimary(); // 执行数据处理

//========================== 地图布局设置 ==========================//
// 设置地图居中
const setCenter = (map) => {
  map.rotation.x = -Math.PI / 2; // 绕X轴旋转90度，使地图水平展示
  // 创建包围盒并计算中心点
  const box = new THREE.Box3().setFromObject(map);
  const centerMap = box.getCenter(new THREE.Vector3());

  const offset = [0, 0]; // 可以设置额外偏移量
  // 调整地图位置使其居中
  map.position.x = map.position.x - centerMap.x - offset[0];
  map.position.z = map.position.z - centerMap.z - offset[1];
};

// 引用TresGroup组件
const tgRef = ref();

// 监听组件挂载，设置地图居中并计算包围树
watchEffect(() => {
  if (tgRef.value) {
    setCenter(tgRef.value);
    // 为所有网格计算包围树，优化射线检测性能
    tgRef.value.children.forEach((item) => {
      if (item.type === "Mesh") {
        item.geometry.computeBoundsTree();
      }
    });
  }
});

//========================== 交互事件处理 ==========================//
// 记录上一个悬停的对象
let lastHoveredObject = null;
// 防抖延迟时间（毫秒）
const debounceDelay = 25;
// 防抖定时器
let debounceTimer = null;
// 动画实例记录
const animations = new Map();

// 鼠标悬停进入区域时的交互效果
const pEnter = (intersection) => {
  // 清除任何现有的定时器
  if (debounceTimer) {
    clearTimeout(debounceTimer);
  }

  debounceTimer = setTimeout(() => {
    // 如果已经悬停在同一对象上，不做任何操作
    if (lastHoveredObject === intersection.object) {
      return;
    }

    // 如果之前有悬停的对象，先恢复它的状态
    if (lastHoveredObject) {
      // 找到之前对象的动画并停止
      const prevAnim = animations.get(lastHoveredObject.uuid);
      if (prevAnim) prevAnim.kill();

      // 如果保存了原始高度，创建下降动画
      if (lastHoveredObject.userData.originalPositionZ !== undefined) {
        const animation = gsap.to(lastHoveredObject.position, {
          z: lastHoveredObject.userData.originalPositionZ,
          duration: props.heightAnimationDuration,
          ease: props.heightAnimationEase,
          onComplete: () => {
            animations.delete(lastHoveredObject.uuid);
          },
        });
        animations.set(lastHoveredObject.uuid, animation);
      }

      // 发送区域离开事件
      emit("region-leave", { name: lastHoveredObject.name });
    }

    // 设置当前悬停对象
    lastHoveredObject = intersection.object;

    // 保存原始高度（如果尚未保存）
    if (intersection.object.userData.originalPositionZ === undefined) {
      intersection.object.userData.originalPositionZ =
        intersection.object.position.z;
    }

    // 创建升起动画
    const targetZ =
      intersection.object.userData.originalPositionZ +
      props.heightHoverIncrement;
    const animation = gsap.to(intersection.object.position, {
      z: targetZ,
      duration: props.heightAnimationDuration,
      ease: props.heightAnimationEase,
      onComplete: () => {
        // 动画完成后可以移除
        animations.delete(intersection.object.uuid);
      },
    });
    animations.set(intersection.object.uuid, animation);

    // 设置鼠标为小手状态
    document.body.style.cursor = "pointer";

    // 获取悬停区域数据并发送到父组件
    const regionName = intersection.object.name;
    const hoverData = { name: regionName };

    // 尝试获取更多区域数据
    areaList.forEach((item) => {
      if (item.type === "Shape" && item.name === regionName) {
        hoverData.value = item.value;
        hoverData.depth = item.depth;
        // 可以添加更多属性
      }
    });

    emit("region-hover", hoverData);
  }, debounceDelay);
};

// 鼠标悬停离开区域时的交互效果
const pLeave = (intersection) => {
  // 清除任何现有的定时器
  if (debounceTimer) {
    clearTimeout(debounceTimer);
  }

  debounceTimer = setTimeout(() => {
    // 确保我们使用正确的对象引用
    const targetObject = intersection.object || intersection.eventObject;

    // 如果离开的不是当前悬停的对象，不做操作
    if (lastHoveredObject !== targetObject) {
      return;
    }

    // 发送区域离开事件（在动画开始前发送）
    if (lastHoveredObject) {
      emit("region-leave", { name: lastHoveredObject.name });
    }

    // 找到当前对象的动画并停止
    const currentAnim = animations.get(lastHoveredObject.uuid);
    if (currentAnim) currentAnim.kill();

    // 如果保存了原始高度，创建下降动画
    if (
      lastHoveredObject &&
      lastHoveredObject.userData.originalPositionZ !== undefined
    ) {
      const animation = gsap.to(lastHoveredObject.position, {
        z: lastHoveredObject.userData.originalPositionZ,
        duration: props.heightAnimationDuration,
        ease: props.heightAnimationEase,
        onComplete: () => {
          animations.delete(lastHoveredObject.uuid);
          lastHoveredObject = null; // 动画完成后才清除引用
          document.body.style.cursor = "default"; // 动画完成后恢复鼠标样式
        },
      });
      animations.set(lastHoveredObject.uuid, animation);
    } else {
      // 如果没有原始高度记录，直接清除引用
      lastHoveredObject = null;
      document.body.style.cursor = "default";
    }
  }, debounceDelay);
};

// 鼠标点击区域时，相机飞行到该位置
const pClick = (intersection) => {
  // 获取点击区域的名称和数据
  const regionName = intersection.object.name;

  // 从区域对象中提取数据
  const regionData = {
    name: regionName,
    position: intersection.point.clone(), // 点击位置的3D坐标
    value: null, // 将在下面设置实际值
    originalObject: intersection.object, // 原始3D对象引用
  };

  // 尝试获取更多区域数据
  // 从userData或模型的构建数据中查找
  areaList.forEach((item) => {
    if (item.type === "Shape" && item.name === regionName) {
      regionData.value = item.value;
      regionData.depth = item.depth;
      regionData.color = item.color;
      // 可以添加更多属性
    }
  });

  // 发送点击事件到父组件
  emit("region-click", regionData);

  // 创建目标位置向量 (原有的相机飞行功能)
  const targetPosition = new THREE.Vector3();
  targetPosition.x = intersection.point.x;
  targetPosition.y = intersection.point.y + 10; // 相机位置比交点高10个单位
  targetPosition.z = intersection.point.z;

  // 根据props中enableFlyTo属性决定是否执行飞行动画
  if (props.enableFlyTo) {
    // 执行相机飞行动画
    twInstant = flyTo(camera, targetPosition, controls);
  }
};

//========================== 场景与动画控制 ==========================//
// 获取场景相机和控制器
const { camera, controls } = useTresContext();

// 存储动画实例
let twInstant = null;

// 获取渲染循环钩子
const { onBeforeLoop } = useRenderLoop();

// 在每帧渲染前更新动画（如果存在）
onBeforeLoop(() => {
  twInstant?.update();
});

//========================== UI配置 ==========================//
// 计算文本样式
const labelStyle = computed(() => {
  return {
    fontSize: `${props.labelFontSize}px`,
    fontFamily: props.labelFontFamily,
    fontWeight: props.labelFontWeight,
    textShadow: "1px 1px 2px #c92704",
    color: "#fff",
  };
});

// HTML标签配置
const htmlState = {
  wrapperClass: "wrapper",
  as: "div",
  center: true,
  sprite: true,
  prepend: true,
  transform: true,
};

//========================== 组件事件定义 ==========================//
// 定义事件发射器
const emit = defineEmits([
  "region-click", // 地区点击事件
  "region-hover", // 地区悬停事件（可选）
  "region-leave", // 地区离开事件（可选）
]);
</script>

<style lang="scss" scoped>
/* 文本标签样式 */
.wrapper {
  #inner {
    user-select: none; // 禁止文本选择
    pointer-events: none !important; // 禁止鼠标事件，使文本标签不会捕获鼠标事件

    span {
      text-shadow: 1px 1px 2px #c92704; // 文本阴影效果，增强可读性
      color: #fff; // 文本颜色为白色
      font-size: 14px; // 文本大小
      font-family: "Arial", sans-serif; // 字体系列
      font-weight: bold; // 字体粗细
      letter-spacing: 0.5px; // 字母间距
    }
  }
}
</style>
